﻿namespace EyeOpen.IO
{
    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;

    /// <summary>
    /// A wrapper of <see cref="SymmetricAlgorithm"/> classes
    /// and simplifies the interface.
    /// </summary>
    public class SymmetricEncryptor
    {
        private readonly SymmetricAlgorithm cryptoService;

        /// <remarks>
        /// Constructor for using an intrinsic .Net symmetric algorithm class.
        /// </remarks>
        public SymmetricEncryptor(SymmetricProvider symmetricProvider)
        {
            switch (symmetricProvider)
            {
                case SymmetricProvider.DES:
                    cryptoService = new DESCryptoServiceProvider();
                    break;
                case SymmetricProvider.RC2:
                    cryptoService = new RC2CryptoServiceProvider();
                    break;
                case SymmetricProvider.Rijndael:
                    cryptoService = new RijndaelManaged();
                    break;
            }
        }

        /// <remarks>
        /// Constructor for using a customized symmetric algorithm class.
        /// </remarks>
        public SymmetricEncryptor(SymmetricAlgorithm serviceProvider)
        {
            cryptoService = serviceProvider;
        }

        public string InitializationVector
        {
            get;
            set;
        }

        public string Encrypting(string source, string key)
        {
            byte[] buffer;
            cryptoService.Key = GetLegalKey(key);
            var encryptor = cryptoService.CreateEncryptor();
            using (var memorystream = new MemoryStream())
            {
                using (var cryptostream = new CryptoStream(memorystream, encryptor, CryptoStreamMode.Write))
                {
                    using (var streamwriter = new StreamWriter(cryptostream))
                    {
                        streamwriter.WriteLine(source);
                    }
                }

                buffer = memorystream.ToArray();
            }

            InitializationVector = Convert.ToBase64String(cryptoService.IV);
            return Convert.ToBase64String(buffer);
        }

        public string Decrypting(string source, string key, string initializationVector)
        {
            var stringToDecrypt = source.TrimStart("<".ToCharArray());
            stringToDecrypt = stringToDecrypt.TrimStart(initializationVector.ToCharArray());
            stringToDecrypt = stringToDecrypt.TrimStart(">".ToCharArray());

            // convert from Base64 to binary
            var byteIn = Convert.FromBase64String(stringToDecrypt);

            // set the private key
            cryptoService.Key = GetLegalKey(key);

            cryptoService.IV = Convert.FromBase64String(initializationVector);

            // create a Decryptor from the Provider Service instance
            var encrypto = cryptoService.CreateDecryptor();

            using (var memorystream = new MemoryStream(byteIn))
            {
                using (var cryptostream = new CryptoStream(memorystream, encrypto, CryptoStreamMode.Read))
                {
                    using (var streamreader = new StreamReader(cryptostream))
                    {
                        return streamreader.ReadLine();
                    }
                }
            }
        }

        /// <remarks>
        /// Depending on the legal key size limitations of a specific crypto service provider
        /// and length of the private key provided, padding the secret key with space character
        /// to meet the legal size of the algorithm.
        /// </remarks>
        private byte[] GetLegalKey(string key)
        {
            string temp;

            if (cryptoService.LegalKeySizes.Length > 0)
            {
                var moreSize = cryptoService.LegalKeySizes[0].MinSize;
                while (key.Length * 8 > moreSize)
                {
                    moreSize += cryptoService.LegalKeySizes[0].SkipSize;
                }

                temp = key.PadRight(moreSize / 8, ' ');
            }
            else
            {
                temp = key;
            }

            return Encoding.ASCII.GetBytes(temp);
        }
    }
}